/* ========================================================================
** Extended Template and Library
** Angle Abstraction Class Implementation
**
** Copyright (c) 2002 Robert B. Quattlebaum Jr.
** Copyright (c) 2007 Chris Moore
**
** This package is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License as
** published by the Free Software Foundation; either version 2 of
** the License, or (at your option) any later version.
**
** This package is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** General Public License for more details.
**
** === N O T E S ===========================================================
**
** This is an internal header file, included by other ETL headers.
** You should not attempt to use it directly.
**
** ========================================================================= */

#ifndef __ETL__ANGLE_H
#define __ETL__ANGLE_H

#include <cstdio>
#include <cmath>
#include <functional>

#ifndef PI
# define PI (3.1415926535897932384626433832795029L)
# define HALF_PI (PI/2)
#endif

#define ANGLE_EPSILON (1.0e-6)

_ETL_BEGIN_NAMESPACE

/*!	\class	angle	_angle.h	ETL/angle
**		Abstraction of the concept of an angle
**  \see angle::deg, angle::rad, angle::rot, angle::sin, angle::cos, angle::tan, fastangle
**	\writeme
*/
class angle
{
public:
    typedef float value_type;

protected:
    typedef value_type unit;

    unit v;	// Stored in radians; positive values indicate counter-clockwise.

public:

    /*
    ** Arithmetic Operators
    */

    const angle	&
    operator+=(const angle &rhs)
    {
        v += rhs.v;
        return *this;
    }

    const angle	&
    operator-=(const angle &rhs)
    {
        v -= rhs.v;
        return *this;
    }

    const angle	&
    operator*=(const unit &rhs)
    {
        v *= rhs;
        return *this;
    }

    const angle	&
    operator/=(const unit &rhs)
    {
        v /= rhs;
        return *this;
    }

    // Angle Addition Operator
    angle
    operator+(const angle &rhs)const
    {
        return angle(*this) += rhs;
    }

    // Angle Subtraction Operator
    /*! \sa angle dist(const angle &) */
    angle
    operator-(const angle &rhs)const
    {
        return angle(*this) -= rhs;
    }

    // Angle Scalar Multiplication Operator
    /*! This operator will multiply the given
    	angle by the given scalar value. */
    angle
    operator*(const unit &rhs)const
    {
        return angle(*this) *= rhs;
    }

    angle
    operator/(const unit &rhs)const
    {
        return angle(*this) /= rhs;
    }

    // Angle Negation
    angle
    operator-()const
    {
        angle ret;
        ret.v = -v;
        return ret;
    }

#ifdef ETL_NOT_USED
    // 180 degree rotation operator
    /*! Returns the angle directly opposite of
    	the given angle, and will yield a result
    	between 0 and 2PI */
    angle
    operator~()const
    {
        angle ret;
        ret.v = v + PI;
        return ret.mod();
    }
#endif // ETL_NOT_USED

#ifdef ETL_WRAP_ANGLES
    /*! Returns true if the shortest
    	angle from the left-hand to the
    	right-hand side is counter-clockwise */
    bool
    operator<(const angle &rhs)const
    {
        return dist(rhs).v < (value_type)0.0;
    }

    /*! Returns true if the shortest
    	angle from the left-hand to the
    	right-hand side is clockwise */
    bool
    operator>(const angle &rhs)const
    {
        return dist(rhs).v > (value_type)0.0;
    }

    /*! Returns true if the shortest
    	angle from the left-hand to the
    	right-hand side is counter-clockwise,
    	or if the angles are refer to the same
    	point on the unit circle. */
    bool
    operator<=(const angle &rhs)const
    {
        return dist(rhs).v <= (value_type)0.0;
    }

    /*! Returns true if the shortest
    	angle from the left-hand to the
    	right-hand side is clockwise,
    	or if the angles are refer to the same
    	point on the unit circle. */
    bool
    operator>=(const angle &rhs)const
    {
        return dist(rhs).v >= (value_type)0.0;
    }

    /*! Returns true if the angles
    	are refer to the same point
    	on the unit circle. */
    bool
    operator==(const angle &rhs)const
    {
        return std::abs(dist(rhs).v) < ANGLE_EPSILON;
    }

    /*! Returns false if the angles
    	are refer to the same point
    	on the unit circle. */
    bool
    operator!=(const angle &rhs)const
    {
        return std::abs(dist(rhs).v) > ANGLE_EPSILON;
    }
#else // ETL_WRAP_ANGLES
    /*! Returns true if the left-hand
    	side is less than the
    	right-hand side */
    bool
    operator<(const angle &rhs)const
    {
        return v < rhs.v;
    }

    /*! Returns true if the left-hand
    	side is greater than the
    	right-hand side */
    bool
    operator>(const angle &rhs)const
    {
        return v > rhs.v;
    }

    /*! Returns true if the left-hand
    	side is less or equal to the
    	right-hand side */
    bool
    operator<=(const angle &rhs)const
    {
        return v <= rhs.v;
    }

    /*! Returns true if the left-hand
    	side is greater than or equal
    	to the right-hand side */
    bool
    operator>=(const angle &rhs)const
    {
        return v >= rhs.v;
    }

    /*! Returns true if the angles
    	are the same, or close */
    bool
    operator==(const angle &rhs)const
    {
        return std::abs(v - rhs.v) < ANGLE_EPSILON;
    }

    /*! Returns false if the angles
    	are different */
    bool
    operator!=(const angle &rhs)const
    {
        return std::abs(v - rhs.v) > ANGLE_EPSILON;
    }
#endif // ETL_WRAP_ANGLES

    // Absolute Angle Function
    /*! This function will return the
    	absolute value of the angle. */
    angle
    abs()const
    {
        angle ret;
        ret.v = std::abs(v);
        return ret;
    }

#ifdef ETL_WRAP_ANGLES
    // Angle Difference Function
    /*! This function will return the
    	shortest physical distance between
    	two angles, from -PI/2 to PI/2
    	\sa angle operator-(const angle &) */
    angle
    dist(const angle &rhs)const
    {
        angle ret;
        ret.v = v - rhs.v;
        ret.v -= rot_floor(ret.v + PI);
        return ret;
    }

    // Rotation Modulus
    /*! This function will return the
    	value of the angle between 0 and 2PI */
    angle
    mod()const
    {
        angle ret(*this);
        ret.v -= rot_floor(ret.v);
        return ret;
    }
#else // ETL_WRAP_ANGLES
    // Angle Difference Function
    /*! This function will return the
    	difference between
    	two angles, just like
    	\sa angle operator-(const angle &) */
    angle
    dist(const angle &rhs)const
    {
        return angle(*this) -= rhs;
    }

    // Rotation Modulus
    /*! This function will return the
    	value of the angle */
    angle
    mod()const
    {
        angle ret(*this);
        return ret;
    }
#endif // ETL_WRAP_ANGLES

    // Zero Rotation (0 degrees)
    static angle
    zero()
    {
        angle ret;
        ret.v = 0;
        return ret;
    }

    // One Complete Rotation (360 degrees)
    static angle
    one()
    {
        angle ret;
        ret.v = PI * 2;
        return ret;
    }

    // One Half Rotation (180 degrees)
    static angle
    half()
    {
        angle ret;
        ret.v = PI;
        return ret;
    }

    bool operator!()const
    {
        return std::abs(mod().v) < ANGLE_EPSILON;
    }

private:

#ifdef ETL_WRAP_ANGLES
    static value_type rot_floor(value_type x)
    {
        return static_cast<value_type>(std::floor(x / (PI * 2)) * PI * 2);
    }
#endif // ETL_WRAP_ANGLES

public:
    /*
    ** Conversion Classes
    */

    class rad;
    class deg;
    class rot;

    /*
    ** Trigonometric Classes
    */

    class sin;
    class cos;
    class tan;

    /*
    ** Friend classes
    */

    friend class rad;
    friend class deg;
    friend class rot;
    friend class sin;
    friend class cos;
    friend class tan;

    /*
    ** Deprecated
    */

#ifndef ETL_NO_DEPRECATED
    typedef rad		radians;
    typedef deg		degrees;
    typedef rot		rotations;
#endif
}; // END of class angle

/*!	\class	angle::rad	_angle.h	ETL/angle
**		Angle representation in radians
**	\see angle
**	\writeme
*/
class angle::rad : public angle
{
public:
    explicit rad(const value_type &x)
    {
        v = x;
    }
    rad(const angle &a): angle(a) { }
    rad	mod()const
    {
        return angle::mod();
    }
    rad dist(const angle &rhs)const
    {
        return angle::dist(rhs);
    }
    value_type get()const
    {
        return v;
    }
#ifndef ETL_NO_DEPRECATED
    // operator value_type()const ETL_DEPRECATED_FUNCTION;
#endif
}; // END of class angle::radians

/*!	\class	angle::deg	_angle.h	ETL/angle
**		Angle representation in degrees
**	\see angle
**	\writeme
*/
class angle::deg : public angle
{
public:
    explicit deg(const value_type &x)
    {
        v = x * ((PI * 2) / 360);
    }
    deg(const angle &a): angle(a) { }
    deg	mod()const
    {
        return angle::mod();
    }
    deg dist(const angle &rhs)const
    {
        return angle::dist(rhs);
    }
    value_type get()const
    {
        return v * 360 / (PI * 2);
    }
#ifndef ETL_NO_DEPRECATED
    // operator value_type()const ETL_DEPRECATED_FUNCTION;
#endif
}; // END of class angle::degrees

/*!	\class	angle::rot	_angle.h	ETL/angle
**		Angle representation in rotations
**	\see angle
**	\writeme
*/
class angle::rot : public angle
{
public:
    explicit rot(const value_type &x)
    {
        v = x * (PI * 2);
    }
    rot(const angle &a): angle(a) { }
    rot mod()const
    {
        return angle::mod();
    }
    rot dist(const angle &rhs)const
    {
        return angle::dist(rhs);
    }
    value_type get()const
    {
        return v / (PI * 2);
    }
#ifndef ETL_NO_DEPRECATED
    // operator value_type()const ETL_DEPRECATED_FUNCTION;
#endif
}; // END of class angle::rotations

/*!	\class	angle::sin	_angle.h	ETL/angle
**		Angle representation as a sine function
**	\see angle
**	\writeme
*/
class angle::sin : public angle
{
public:
    explicit sin(const value_type &x)
    {
        v = static_cast<value_type>(std::asin(x));
    }
    sin(const angle &a): angle(a) { }
    sin	mod()const
    {
        return angle::mod();
    }
    sin dist(const angle &rhs)const
    {
        return angle::dist(rhs);
    }
    value_type get()const
    {
        return static_cast<value_type>(std::sin(v));
    }
#ifndef ETL_NO_DEPRECATED
    // operator value_type()const ETL_DEPRECATED_FUNCTION;
#endif
}; // END of class angle::sin

/*!	\class	angle::cos	_angle.h	ETL/angle
**		Angle representation as a cosine function
**	\see angle
**	\writeme
*/
class angle::cos : public angle
{
public:
    explicit cos(const value_type &x)
    {
        v = (value_type)(std::acos(x));
    }
    cos(const angle &a): angle(a) { }
    cos	mod()const
    {
        return angle::mod();
    }
    cos dist(const angle &rhs)const
    {
        return angle::dist(rhs);
    }
    value_type get()const
    {
        return (value_type)std::cos(v);
    }
#ifndef ETL_NO_DEPRECATED
    // operator value_type()const ETL_DEPRECATED_FUNCTION;
#endif
}; // END of class angle::cos

/*!	\class	angle::tan	_angle.h	ETL/angle
**		Angle representation as a tangent function
**	\see angle
**	\writeme
*/
class angle::tan : public angle
{
public:
    explicit tan(const value_type &x)
    {
        v = (value_type)(std::atan(x));
    }
    tan(const value_type &y, const value_type &x)
    {
        v = (value_type)(std::atan2(y, x));
    }
    tan(const angle &a): angle(a) { }
    tan	mod()const
    {
        return angle::mod();
    }
    tan dist(const angle &rhs)const
    {
        return angle::dist(rhs);
    }
    value_type get()const
    {
        return (value_type)std::tan(v);
    }
#ifndef ETL_NO_DEPRECATED
    // operator value_type()const ETL_DEPRECATED_FUNCTION;
#endif
}; // END of class angle::tan

_ETL_END_NAMESPACE

template <typename T>
struct affine_combo<etl::angle, T> {
    typedef T time_type;


    etl::angle operator()(const etl::angle &a, const etl::angle &b, const time_type &t)const
    {
        return b.dist(a) * (float)t + a;
    }

    etl::angle reverse(const etl::angle &x, const etl::angle &b, const time_type &t)const
    {
        return x.dist(b * (float)t) * (float)(time_type(1) / (time_type(1) - t));
    }
};

template <>
struct distance_func<etl::angle> : public std::binary_function<etl::angle, etl::angle, etl::angle> {
    etl::angle operator()(const etl::angle &a, const etl::angle &b)const
    {
        etl::angle delta = b.dist(a);
        return delta;
    }

    etl::angle cook(const etl::angle &x)const
    {
        return x;
    }
    etl::angle uncook(const etl::angle &x)const
    {
        return x;
    }
};

#endif